package com.apobates.forum.orize.core;

import com.apobates.forum.orize.OrizeExecutor;
import com.apobates.forum.orize.OrizeExpression;
import com.apobates.forum.orize.OrizeMatchResult;
import com.apobates.forum.orize.OrizeResource;
import com.apobates.forum.orize.core.plug.*;
import net.sf.saxon.lib.NamespaceConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.util.*;

/**
 * 使用W3C的Document来作执行器
 * @author xiaofanku@live.cn
 * @since 20210822
 */
public class OrizeClassicalExecutor extends OrizeExecutor {
    //生成缓存的树
    private Document doc = null;
    private OrizeSlotResourceMatchStrategy slotMatchStrategy;
    private static OrizeClassicalExecutor ins=null;
    private final static Logger logger = LoggerFactory.getLogger(OrizeClassicalExecutor.class);

    static{
        logger.info("[OE-XML]set global system property");
        System.setProperty("javax.xml.xpath.XPathFactory:" + NamespaceConstant.OBJECT_MODEL_SAXON, "net.sf.saxon.xpath.XPathFactoryImpl");
    }

    /**
     * 执行器
     * @param loader 资源加载器
     */
    private OrizeClassicalExecutor(OrizeResourceLoader loader){
        super();
        try {
            this.doc=loader.load();
            this.slotMatchStrategy = new OrizeSlotResourceAntPathMatchStrategy();
                                   //new OrizeSlotResourceMatchGeneralStrategy();
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage());
        }
    }

    public static synchronized OrizeClassicalExecutor getInstance(OrizeResourceLoader loader){
        if(null == ins){
            ins = new OrizeClassicalExecutor(loader);
        }
        return ins;
    }

    /**
     * 1.加载手动定义验证的资源
     * 适用于少量的需要验证的资源或控制器方法未用Orize注解
     * @param jsonFilePath json文件路径
     * @return
     */
    public static OrizeResourceLoader json(String jsonFilePath){
        return new OrizeResourceJsonLoader(jsonFilePath);
    }

    /**
     * 1.加载手动定义验证的资源
     * 适用于少量的需要验证的资源或控制器方法未用Orize注解
     * @param xmlFilePath xml文件路径
     * @return
     */
    public static OrizeResourceLoader xml(String xmlFilePath){
        return new OrizeResourceXmlLoader(xmlFilePath);
    }

    /**
     * 1.加载注解自动定义的资源
     * @return
     */
    public static OrizeResourceLoader annotation(String relativeDirectoryPath){
        return new OrizeResourceXmlLoader(relativeDirectoryPath+""+OrizeResourceXmlLoader.ANNO_RES_DEF_FILENAME);
    }

    @Override
    protected OrizeMatchResult strictMatch(String reqPath, String reqQueryString, String reqMethod, final OrizeExpression expression) {
        Objects.requireNonNull(doc);
        Optional<NodeList> optNl = matchResourcePathByXpath2(reqPath, reqMethod);//:matchResourcePath(reqPath, reqMethod);
        if(optNl.isPresent()) { //严格模式匹配找到了
            NodeList nl = optNl.get();
            if (nl.getLength() == 0) { //没有定义.放行
                return null;
            }
            //查询字符串可能为null或不存在
            Optional<String> reqQuery = Optional.ofNullable(reqQueryString);
            OrizeResource decision = decision(nl, reqQuery);
            //3.3 验证权限
            return new OrizeMatchResult.Builder(expression).next(decision);
        }
        return null;
    }

    @Override
    protected OrizeMatchResult slotMatch(String reqPath, String reqQueryString, String reqMethod, final OrizeExpression expression) {
        Objects.requireNonNull(doc);
        Optional<NodeList> nl = slotMatchURL(reqMethod);
        //偿试进行占位符匹配
        Optional<OrizeResource> optRes = decisionSlotMatch(nl, reqPath);
        if(optRes.isPresent()){
            OrizeResource slotRes = optRes.get();
            return new OrizeMatchResult.Builder(expression).next(slotRes);
        }
        return null;
    }

    public void destroy(){
        Objects.requireNonNull(this.doc);
        this.doc = null;
    }

    /**
     * 匹配指定请求方法下的所有是占位符的资源定义
     *
     * @param requestMethod 请求方法
     * @return
     */
    private Optional<NodeList> slotMatchURL(String requestMethod){
        try {
            //stackoverflow.com/questions/54283920/no-xpathfactory-implementation-found-xpath-factory-instance-creation-issue-in-u
            //A workaround is to instantiate Saxon's XPathFactory directly (using new net.sf.saxon.xpath.XPathFactoryImpl()) rather than using the JAXP search mechanism. This is in any case far more efficient.
            XPathFactory xPathFactory = new net.sf.saxon.xpath.XPathFactoryImpl();
            //XPathFactory.newInstance(NamespaceConstant.OBJECT_MODEL_SAXON);
            XPath xPath = xPathFactory.newXPath();
            //找出method=arg[1]并且是包含占位符的定义
            String slotXPath = String.format("/resources/item[lower-case(slot)='true' and lower-case(method)='%s']", requestMethod.toLowerCase());
            NodeList nl = (NodeList)xPath.compile(slotXPath).evaluate(this.doc, XPathConstants.NODESET);
            return Optional.ofNullable(nl);
        }catch (XPathExpressionException e){
            if(logger.isDebugEnabled()){
                logger.debug(e.getMessage(), e);
            }
        }
        return Optional.empty();
    }

    /**
     * 使用请求的method:path来验证是否存在资源定义
     * 注意: 为了解决(method或path)的大小写导致无法匹配资源的问题使用XPath2的函数.需要相关的库支持:
     * //sourceforge.net/projects/saxon/files/
     *
     * @param requestPath 请求路径
     * @param requestMethod 请求的HTTP方法
     * @return
     */
    private Optional<NodeList> matchResourcePathByXpath2(String requestPath, String requestMethod) {
        String qsPath = genXpath(requestPath, requestMethod);
        try {
            XPathFactory xPathFactory = new net.sf.saxon.xpath.XPathFactoryImpl();
            XPath xPath = xPathFactory.newXPath();
            NodeList nl = (NodeList)xPath.compile(qsPath).evaluate(this.doc, XPathConstants.NODESET);
            return Optional.ofNullable(nl);
        }catch (XPathExpressionException e){
            e.printStackTrace();
        }
        return Optional.empty();
    }

    /**
     * 裁决一个唯一的定义/严格全模式
     * @param nl
     * @param requestQuery 请求地址中的查询字符串
     * @return
     */
    private OrizeResource decision(NodeList nl, final Optional<String> requestQuery){
        int matchSize = nl.getLength();
        if(matchSize == 0){
            throw new IllegalStateException("未找到验证的资源定义");
        }
        List<OrizeResource> rs = convertNode(nl);
        if(matchSize == 1){ //若找到一个?
            return rs.get(0);
        }
        if(matchSize > 1 && requestQuery.isPresent()){ //若找到多个?
            final String qsPart = "%s=%s";
            Optional<OrizeResource> tmp = rs.stream().filter(ore->{
                //统一使用小写:20210809
                return requestQuery.get().toLowerCase().contains(String.format(qsPart, ore.getSpot(), ore.getAction()).toLowerCase());
            }).findFirst();
            if(tmp.isPresent()){
                return tmp.get();
            }
        }
        throw new IllegalStateException("决断资源定义失败");
    }

    /**
     * 裁决一个唯一的定义/占位符全模式
     * @param optNl
     * @param requestPath
     * @return
     */
    private Optional<OrizeResource> decisionSlotMatch(Optional<NodeList> optNl, final String requestPath){
        if(!optNl.isPresent()){
            return Optional.empty();
        }
        NodeList nl = optNl.get();
        int matchSize = nl.getLength();
        if(matchSize == 0){
            return Optional.empty();
        }
        for(int i=0;i<nl.getLength();i++){
            Node _n = nl.item(i);
            Element et = (Element)_n;
            //资源中定义的路径信息
            String resourcePath = et.getElementsByTagName("path").item(0).getFirstChild().getNodeValue();
            //资源中定义的占位符信息
            String resourceSlotsKey = et.getElementsByTagName("slotKeys").item(0).getFirstChild().getNodeValue();
            if(slotMatchStrategy.match(requestPath, resourcePath, resourceSlotsKey)){
                return Optional.ofNullable(convertNode(_n));
            }
        }
        return Optional.empty();
    }

    /**
     * 生成严格模式下的xpath表达式(方法+路径)
     *
     * @param requestPath 请求路径
     * @param requestMethod 请求方法
     * @return
     */
    private String genXpath(String requestPath, String requestMethod){
        //统一使用小写:20210809
        //Xpath2: matches
        //return String.format("/resources/item[matches(path, '%s', 'i') and matches(method, '%s', 'i')]", requestPath, requestMethod);
        //Xpath2: lower-case
        return String.format("/resources/item[lower-case(path) = '%s' and lower-case(method) = '%s']", requestPath.toLowerCase(), requestMethod.toLowerCase());
    }

    /**
     * 将匹配到的节点转成对象集合
     *
     * @param nl
     * @return
     */
    private List<OrizeResource> convertNode(NodeList nl){
        List<OrizeResource> rs = new ArrayList<>();
        for(int i=0;i<nl.getLength();i++){
            Node _n = nl.item(i);
            rs.add(convertNode(_n));
        }
        return rs;
    }

    /**
     *
     * @param node
     * @return
     */
    private OrizeResource convertNode(Node node){
        OrizeResource ins = new OrizeResource();
        Element et = (Element)node;
        String action = et.getElementsByTagName("action").item(0).getFirstChild().getNodeValue();
        String path = et.getElementsByTagName("path").item(0).getFirstChild().getNodeValue();
        String method = et.getElementsByTagName("method").item(0).getFirstChild().getNodeValue();
        String roles = et.getElementsByTagName("roles").item(0).getFirstChild().getNodeValue();
        String spot = et.getElementsByTagName("spot").item(0).getFirstChild().getNodeValue();
        ins.setAction(action);
        ins.setPath(path);
        ins.setMethod(method);
        ins.setRoles(roles);
        ins.setSpot(spot);
        return ins;
    }
}
